/*
 * Copyright (c) 2014 pkzd/1.0, 颜文泽(robin)
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     1 Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     2 Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include	<fs.h>

int
creat(const char *pathname, mode_t mode)
{
	return open(pathname, O_WRONLY | O_CREAT, mode);
}

int
open(const char *pathname, int flags, mode_t mode)
{
	uint i;
	struct file *fp;
	struct inode *ip;

	if(flags & O_CREAT && !S_ISREG(mode)){
		up->p_error = EINVAL;
		return -1;
	}
	ip = namei(pathname, 0, 0);
	if(flags & O_CREAT){
		if(flags & O_EXECL && ip)
			goto out;
		if(ip == NULL){
			if(ip = namei(pathname, 1, 0)){
				ip->i_nlink = 1;
        			ip->i_mode = mode;
        			ip->i_uid = up->p_uid;
        			ip->i_gid = up->p_gid;
				ip->i_flag |= IUPD | IACC | ICHG;
				iupdat(ip);
			}else
				return -1;
		}else{
			itrunc(ip);		
			iupdat(ip);
		}
			
	}else{
		if(ip == NULL){
			up->p_error = ENOENT;
			return -1;
		}
	}
	if(flags & O_RDONLY && access0(ip, S_IRUSR)){
		up->p_error = EACCES;
		goto out;
	}
	if(flags & O_WRONLY && access0(ip, S_IWUSR)){
		up->p_error = EACCES;
		goto out;
	}
	for(i = 0; i < NOFILE; i++){
                if(up->p_ofile[i] == NULL)
                        break;
        }
        if(i == NOFILE){
		up->p_error = EMFILE;
		goto out;
	}
        for(fp = &file[0]; fp < &file[NFILE]; fp++){
                if(fp->f_count == 0){
                        up->p_ofile[i] = fp;
                        fp->f_count++;
                        fp->f_offset = flags & O_APPEND ? ip->i_size: 0;
			fp->f_inodep = ip;
			fp->f_flag = flags;
			break;
                }
        }
	if(fp == &file[NFILE])
		goto out;
	ip->i_flag &= ~ILOCK;
	if(ip->i_flag & IWANT){
		ip->i_flag &= ~IWANT;
		wakeup(ip);
	}
	return i;
out:
	if(ip)
		iput(ip);
	return -1;
}

int
close(int fd)
{
	struct file *fp;

	if(fd < 0 || !(fp = up->p_ofile[fd])){
		up->p_error = EBADF;
		return -1;
	}
	if(fp->f_count == 1)
		iput(fp->f_inodep);
	fp->f_count--;
	up->p_ofile[fd] = NULL;
	return 0;
}

int
dup(int fd)
{
	uint i;
	struct file *fp;

	if(fd < 0 || !(fp = up->p_ofile[fd])){
		up->p_error = EBADF;
		return -1;
	}
	for(i = 0; i < NOFILE; i++){
                if(up->p_ofile[i] == NULL)
                        break;
        }
	if(i == NOFILE){
		up->p_error = EMFILE;
		return -1;
	}
	fp->f_count++;
	up->p_ofile[i] = fp;
	return i;
}

int
read(int fd, char *buf, size_t cnt)
{
	int rtv;
	uint i, n, bn;
	char tmp[BLKSIZ];
	struct file *fp;
	struct inode *ip;

	if(fd < 0 || !(fp = up->p_ofile[fd]) || !(ip = fp->f_inodep)){
		up->p_error = EBADF;
		return -1;
	}
	if(!(fp->f_flag & O_RDONLY) || access0(ip, S_IRUSR)){
		up->p_error = EACCES;
		return -1;
	}
	if(S_ISCHR(ip->i_mode))
                return cnsread(ip->i_dev, buf, cnt);
	if(S_ISBLK(ip->i_mode)) 
		return 0;
	if(cnt == 0)
		return 0;
	rtv = -1;
	while(ip->i_flag & ILOCK){
		ip->i_flag |= IWANT;
		sleep(ip, PINOD);	
	}
	ip->i_flag |= ILOCK;
	if(fp->f_offset + cnt > ip->i_size){ 
		if(!(cnt = ip->i_size - fp->f_offset)){
			rtv = 0;
			goto out;
		}
	}
	i = 0;
	bn = (fp->f_offset & BLK_MASK) >> BLK_OFF;
	if(fp->f_offset % BLKSIZ){ 
		if(log_read(bn, tmp, ip, 1) != 1){
			rtv = 0;
			goto out;
		}
		if((i = BLKSIZ - fp->f_offset % BLKSIZ) > cnt)
			i = cnt;
		memmove(buf, tmp + fp->f_offset % BLKSIZ, i);
		bn++;
		fp->f_offset += i; 
	}
	if((cnt - i) / BLKSIZ){
		n = log_read(bn, buf + i, ip, (cnt - i) / BLKSIZ);
		fp->f_offset += n * BLKSIZ;
		if(n < (cnt - i) / BLKSIZ){
			rtv = i + n * BLKSIZ;
			goto out;
		}
		bn += n;
		i += n * BLKSIZ;
	}
	if(cnt - i){
		if(log_read(bn, tmp, ip, 1) != 1){
			rtv = i;
			goto out;
		}
		memmove(buf + i, tmp, cnt - i);	
		fp->f_offset += cnt - i;
	}
	rtv = cnt;
out:
	if(rtv > 0){
		ip->i_flag |= IACC;
		iupdat(ip);
	}
	ip->i_flag &= ~ILOCK;
	if(ip->i_flag & IWANT){
		ip->i_flag &= ~IWANT;
		wakeup(ip);
	}
	return rtv;
}

int
write(int fd, const char *buf, size_t cnt)
{
	int rtv;
	uint i, n, bn;
	char tmp[BLKSIZ];
	struct file *fp;
	struct inode *ip;

 	if(fd < 0 || !(fp = up->p_ofile[fd]) || !(ip = fp->f_inodep)){
                up->p_error = EBADF;
                return -1;
        }
	if(!(fp->f_flag & O_WRONLY) || access0(ip, S_IWUSR)){
		up->p_error = EACCES;
		return -1;
	}
  	if(S_ISCHR(ip->i_mode))
                return cnswrite(ip->i_dev, buf, cnt);
	if(S_ISBLK(ip->i_mode))
                return 0;
	if(cnt == 0)
		return 0;
	rtv = -1;
	while(ip->i_flag & ILOCK){
		ip->i_flag |= IWANT;
		sleep(ip, PINOD);
	}
	ip->i_flag |= ILOCK;
	if(fp->f_offset + cnt > ip->i_size){
		ip->i_size = fp->f_offset + cnt;
		ip->i_flag |= ICHG;
	}
	i = 0;
	bn = (fp->f_offset & BLK_MASK) >> BLK_OFF;
	if(fp->f_offset % BLKSIZ){ 
		if(log_read(bn, tmp, ip, 1) != 1){
			rtv = 0;
			goto out;
		}
		if((i = BLKSIZ - fp->f_offset % BLKSIZ) > cnt)
			i = cnt;
		memmove(tmp + fp->f_offset % BLKSIZ, buf, i);
		if(log_write(bn, tmp, ip, 1) != 1){
			rtv = 0;
			goto out;
		}
		bn++;
		fp->f_offset += i;
	}
	if((cnt - i) / BLKSIZ){
		n = log_write(bn, buf + i, ip, (cnt - i) / BLKSIZ);
		fp->f_offset += n * BLKSIZ;
		if(n < (cnt - i) / BLKSIZ){
			rtv = i + n * BLKSIZ;
			goto out;
		}
		bn += n;
		i += n * BLKSIZ;
	}
        if(cnt - i){
                if(log_read(bn, tmp, ip, 1) != 1){
                        rtv = i;
                        goto out;
                }
		memmove(tmp, buf + i, cnt - i);
		if(log_write(bn, tmp, ip, 1) != 1){
			rtv = i;
			goto out;
		}
                fp->f_offset += cnt - i;
        }
	rtv = cnt;
out:
	if(rtv > 0){
		ip->i_flag |= IUPD;
		iupdat(ip);
	}
	ip->i_flag &= ~ILOCK;
	if(ip->i_flag & IWANT){
		ip->i_flag &= ~IWANT;
		wakeup(ip);
	}
	return rtv;
}

off_t
lseek(int fd, off_t offset, int whence)
{
	struct file *fp;
	struct inode *ip;

	if(!((fp = up->p_ofile[fd]) && (ip = fp->f_inodep))){
                up->p_error = EBADF;
                return -1;
        }
	switch(whence){
	case SEEK_SET:
		if(offset < 0){
			up->p_error = EINVAL;
			return -1;
		}	
		return fp->f_offset = offset;
	case SEEK_CUR:
		if(fp->f_offset + offset < 0){
			up->p_error = EINVAL;
			return -1;
		}
		return fp->f_offset += offset;
	case SEEK_END:
		if((off_t )ip->i_size + offset < 0){	
			up->p_error = EINVAL;
			return -1;
		}
		return fp->f_offset = ip->i_size + offset;
	default:
		return -1;
	}
}

int
mknod(const char *pathname, mode_t mode, dev_t dev)
{
	struct direct dir[2];
	struct inode *ip, *dp;

	if(S_ISDIR(mode)){
		if(ip = namei(pathname, 1, 0)){
			up->p_error = EEXIST;
			return -1;
		}
		if(!(dp = namei(pathname, 3, 0))){
			iput(ip);
			up->p_error = ENOENT;
			return -1;
		}
		ip->i_nlink = 2;
		ip->i_mode = mode;
		ip->i_flag |= IACC | IUPD | ICHG;
		dir[0].d_ino = ip->i_number;
		memmove(dir[0].d_name, ".", 2);
		dir[1].d_ino = dp->i_number;
		memmove(dir[1].d_name, "..", 2);
		ioadd(ip, dir, sizeof(dir)); 
		dp->i_nlink++;
		dp->i_flag |= ICHG;
		iput(dp);
		iput(ip);
		return 0;
	}else if(S_ISCHR(mode) || S_ISBLK(mode)){
		if(ip = namei(pathname, 1, 0)){
			up->p_error = EEXIST;
			return -1;
		}
		ip->i_nlink = 1;
		ip->i_mode = mode;
		ip->i_addr[0] = dev;
		ip->i_flag |= IACC | IUPD | ICHG;
		iput(ip);
		return 0;
	}else{
		up->p_error = EINVAL;
		return -1;
	}
}

int
access(const char *pathname, mode_t mode)
{
	int rtv;
	struct inode *ip;		

	if(ip = namei(pathname, 0, 0)){
		if(mode == O_EXIT){
			iput(ip);
			return 0;
		}
		rtv = access0(ip, mode);
		iput(ip);
		return rtv;
	}
	up->p_error = ENOENT;
	return -1;
}

int
link(const char *oldpath, const char *newpath)
{
	ino_t ino;
	struct inode *ip;

	if(ip = namei(newpath, 0, 0)){
		iput(ip);
		up->p_error = EEXIST;
		return -1;
	}
	if(!(ip = namei(oldpath, 0, 0))){
		up->p_error = ENOENT;
		return -1;
	}		
	if(S_ISDIR(ip->i_mode)){
		iput(ip);
		up->p_error = EPERM;
		return -1;
	}
	ino = ip->i_number;
	iput(ip);
	if(!(ip = namei(newpath, 1, ino))){
		up->p_error = EIO;
		return -1;
	}
	ip->i_nlink++;
	ip->i_flag |= ICHG;
	iput(ip);
	return 0;
}

int
unlink(const char *path)
{
	struct inode *ip;

	if(!(ip = namei(path, 0, 0))){
		up->p_error = ENOENT;
		return -1;
	}
	if(S_ISDIR(ip->i_mode)){
		iput(ip);
		up->p_error = EINVAL;
		return -1;
	}
	iput(ip);
	return namei(path, 2, 0) ? 0 : -1;
}

int
mkdir(const char *pathname, mode_t mode)
{
	if(!S_ISDIR(mode)){
		up->p_error = EINVAL;
		return -1;
	}
	return mknod(pathname, mode, 0);
}

int
rmdir(const char *pathname)
{
	struct inode *ip;

	if(!(ip = namei(pathname, 0, 0))){
		up->p_error = ENOENT;
		return -1;
	}
	if(!S_ISDIR(ip->i_mode)){
		iput(ip);
		return -1;
	}
	if(isempty(ip) != 1){
		iput(ip);
		return -1;
	}
	iput(ip);
	return namei(pathname, 2, 0) ? 0 : -1;
}

int
chdir(char *pathname)
{
	struct inode *ip;

	if(!(ip = namei(pathname, 0, 0))){
		up->p_error = ENOENT;
		return -1;
	}
	if(!S_ISDIR(ip->i_mode) || access0(ip, S_IXUSR)){
		iput(ip);
		return -1;
	}
	iput(up->p_cdir);
	up->p_cdir = ip;
	ip->i_flag &= ~ILOCK;
	if(ip->i_flag & IWANT){
		ip->i_flag &= ~IWANT;	
		wakeup(ip);
	}
	return 0;
}
